CLI Improvements: Pagination, Shell Completion, Config, and Debug#383
CLI Improvements: Pagination, Shell Completion, Config, and Debug#383
Conversation
- Add SetRetryCount(3) with retry on 5xx and 429 errors - Add ListResponse[T] struct with Data, TotalCount, HasMore fields - Add getWithPagination() method for paginated requests - Add ListXxxWithPagination() methods for instances, functions, containers, buckets, SSH keys, instance types
- Enable Cobra shell completion generation - Add cloud completion [bash|zsh|fish|powershell] command - Add cloud config show|set|unset command for persistent configuration - Add --output, --debug global flags - Store config in ~/.cloud/config.json with api_key, api_url, output, tenant, debug
- Add --limit and --offset flags to instance list, function list - Display pagination metadata: "Showing X of Y total (more available)" - Add YAML output support via --output yaml flag - Enhance printOutput() to handle table/json/yaml formats
- Add --limit and --offset flags to container list and storage list - Update container_cli_test.go to use Response wrapper format
- Add --limit and --offset flags to both commands - Display pagination metadata when results are paginated
- Add global flags documentation (--output, --api-url, --debug) - Add cloud config command section - Add pagination section with usage examples - Add shell completion section - Add container commands section - Add CLI Enhancements to Recent Improvements in README
- Add ListDeploymentsWithPagination method - Add ListBucketsWithPagination method - Fix test response format to use Response wrapper
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
📝 WalkthroughWalkthroughThis PR introduces server-side pagination across multiple SDK list methods and CLI commands, adds persistent configuration management, shell completion support, and flexible output format selection (JSON/YAML/table) to the CLI. ChangesPagination and CLI Configuration Enhancements
🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Change config file permissions from 0644 to 0600 for security - Remove unused demoPrompt constant from instance.go - Add errFmt constant to common.go and remove duplicate in sg.go - Add listWithPagination helper function for DRY list commands
- Add getContextWithPagination to client.go - Add ListInstancesWithContextAndPagination to compute.go - Add ListFunctionsWithContextAndPagination to function.go - Add ListDeploymentsWithContextAndPagination to container.go - Add ListBucketsWithContextAndPagination to storage.go
- Refactor instance_type.go list command to use listWithPagination helper - Remove unused sdk import from instance_type.go - Add trailing newline to config.go
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| rootCmd.PersistentFlags().StringVarP(&opts.Output, "output", "o", "table", "Output format (table, json, yaml)") | ||
| rootCmd.PersistentFlags().BoolVarP(&opts.JSON, "json", "j", false, "Output in JSON format (deprecated, use --output=json)") | ||
| rootCmd.PersistentFlags().StringVarP(&opts.APIKey, "api-key", "k", "", "API key for authentication") | ||
| rootCmd.PersistentFlags().StringVar(&opts.APIURL, "api-url", "http://localhost:8080", "URL of the API server") | ||
| rootCmd.PersistentFlags().StringVar(&opts.TenantID, "tenant", "", "Tenant ID to use for requests") | ||
| rootCmd.PersistentFlags().BoolVarP(&opts.Debug, "debug", "d", false, "Enable debug mode") |
| } | ||
|
|
||
| var configFile = filepath.Join(os.Getenv("HOME"), ".cloud", "config.json") | ||
|
|
||
| func loadConfigFile() string { |
| var configShowCmd = &cobra.Command{ | ||
| Use: "show", | ||
| Short: "Show current configuration", | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| cfg := loadFullConfig() | ||
| printOutput(cfg) | ||
| }, | ||
| } |
| switch key { | ||
| case "api-key": | ||
| cfg.APIKey = value | ||
| case "api-url": | ||
| cfg.APIURL = value | ||
| case "output": | ||
| cfg.Output = value | ||
| case "tenant": | ||
| cfg.Tenant = value | ||
| default: | ||
| fmt.Printf("Error: unknown config key: %s\n", key) | ||
| fmt.Println("Valid keys: api-key, api-url, output, tenant") | ||
| os.Exit(1) | ||
| } |
| default: | ||
| fmt.Printf("Error: unknown config key: %s\n", key) | ||
| fmt.Println("Valid keys: api-key, api-url, output, tenant") |
| } | ||
| if key == "" { | ||
| key = loadConfig() | ||
| key = loadConfigFile() |
| // listWithPagination is a generic helper for list commands that support pagination. | ||
| // It calls the paginated function if limit/offset are set, otherwise calls the regular function. | ||
| func listWithPagination[T any]( | ||
| regularFn func() ([]T, error), | ||
| pagFn func(int, int) ([]T, *sdk.ListResponse[T], error), | ||
| limit, offset int, | ||
| ) ([]T, *sdk.ListResponse[T], error) { | ||
| if limit > 0 || offset > 0 { | ||
| return pagFn(limit, offset) | ||
| } | ||
| items, err := regularFn() | ||
| return items, nil, err |
| func (c *Client) ListDeployments() ([]Deployment, error) { | ||
| var deps []Deployment | ||
| err := c.get("/containers/deployments", &deps) | ||
| return deps, err | ||
| var res Response[[]Deployment] | ||
| if err := c.get("/containers/deployments", &res); err != nil { | ||
| return nil, err | ||
| } | ||
| return res.Data, nil | ||
| } |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| assert.Equal(t, "/containers/deployments", r.URL.Path) | ||
| assert.Equal(t, http.MethodGet, r.Method) | ||
|
|
||
| w.Header().Set("Content-Type", "application/json") | ||
| _ = json.NewEncoder(w).Encode(expectedDeps) | ||
| _ = json.NewEncoder(w).Encode(Response[[]Deployment]{Data: expectedDeps}) | ||
| })) |
| func TestListDeploymentsCmd(t *testing.T) { | ||
| type listDeploymentsResponse struct { | ||
| Data []map[string]interface{} `json:"data"` | ||
| } | ||
|
|
Skip test that has race condition with miniredis causing 'redis: nil' error. The test fails intermittently due to shared miniredis state in parallel test runs. A proper fix would require refactoring the test to not share state.
We need to see where exactly inside createRoleCmd.Run the crash occurs in CI but not locally. Adding debug output before and after Run call within the captureStdout lambda.
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
cmd/cloud/rbac_cli_test.go (1)
23-68: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winRemove debug artifacts before merging.
Lines 23, 51, 56, 60, 62, 64, 66, and 68 are all temporary
fmt.Fprintf(os.Stderr, "DEBUG: ...")statements that pollute CI output and clearly were not intended to stay in the codebase. The PR description also acknowledges committing "debug commits adding panic recovery and diagnostic prints."🧹 Proposed cleanup
func TestCreateRoleCmd(t *testing.T) { - fmt.Fprintf(os.Stderr, "DEBUG: TestCreateRoleCmd starting\n") server := httptest.NewServer(...) ... - fmt.Fprintf(os.Stderr, "DEBUG: About to call createRoleCmd.Run\n") - var out string - func() { - defer func() { - if r := recover(); r != nil { - fmt.Fprintf(os.Stderr, "DEBUG: panic in Run: %v\n", r) - t.Fatalf("panic in createRoleCmd.Run: %v", r) - } - }() - fmt.Fprintf(os.Stderr, "DEBUG: Before captureStdout\n") - out = captureStdout(t, func() { - fmt.Fprintf(os.Stderr, "DEBUG: Inside captureStdout fn, before Run\n") - createRoleCmd.Run(createRoleCmd, []string{rbacTestRoleName}) - fmt.Fprintf(os.Stderr, "DEBUG: Inside captureStdout fn, after Run\n") - }) - fmt.Fprintf(os.Stderr, "DEBUG: After captureStdout returned\n") - }() - fmt.Fprintf(os.Stderr, "DEBUG: createRoleCmd.Run completed, output length: %d\n", len(out)) + out := captureStdout(t, func() { + createRoleCmd.Run(createRoleCmd, []string{rbacTestRoleName}) + }) if !strings.Contains(out, "Role created") || !strings.Contains(out, rbacTestRoleID) {The
fmtandosimports added on lines 5 and 8 can also be dropped after removing the debug statements.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/rbac_cli_test.go` around lines 23 - 68, Remove the temporary debug prints added to the TestCreateRoleCmd test: delete all fmt.Fprintf(os.Stderr, "DEBUG: ...") calls around the httptest server setup and the captureStdout/panic-recovery block (they surround createRoleCmd.Run and captureStdout); after removing those debug statements, also remove the now-unused fmt and os imports from the top of cmd/cloud/rbac_cli_test.go so there are no leftover compile warnings or CI noise.docs/cli-reference.md (1)
1165-1169:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win"Tips & Tricks" section still promotes
--jsonwithout mentioning its deprecationLine 1166 reads: Use
--json(or-j) with any list/get command… — contradicting the deprecation of--jsondocumented at the top of the file. Consider updating this to reference--output jsonas the preferred form.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/cli-reference.md` around lines 1165 - 1169, The "JSON Output" section references the deprecated flags `--json` and `-j`; update the explanatory text and examples to recommend the supported form `--output json` instead. Change the sentence "Use `--json` (or `-j`) with any list/get command…" to mention `--output json` (and optionally `-o json` if applicable), and replace the example `cloud instance list --json | jq '.[].id'` with one using `cloud instance list --output json | jq '.[].id'` so the doc is consistent with the deprecation notice.cmd/cloud/container.go (1)
38-78:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
listDeploymentsCmdignores--jsonand--output— output format flags are silently no-ops
listDeploymentsCmdalways renders a table.opts.JSONandopts.Outputare never consulted, socloud container list --json,cloud container list --output json, and--output yamlall silently fall through to table output. The same gap exists infunction.go'slistFnCmdandssh_key.go'snewSSHKeyListCmd.The
--outputflag is documented as working for all list commands, making this a functional regression relative to the documented behaviour.🛠️ Suggested fix — add JSON/YAML output path
table.Render() + if opts.JSON || opts.Output == "json" { + printJSON(deps) + return + } + if meta != nil {Or use the shared
printOutput()helper fromcommon.goif it supports the slice type.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/container.go` around lines 38 - 78, The listDeploymentsCmd currently always renders a table and ignores opts.JSON/opts.Output; update listDeploymentsCmd (and mirror the same fix in listFnCmd and newSSHKeyListCmd) to check opts.JSON or opts.Output and branch to emit JSON or YAML when requested (e.g., serialize the deps slice using encoding/json or yaml), or call the shared printOutput() helper if it supports slices; keep the existing table rendering as the default, and ensure error handling and meta output (meta.TotalCount/HasMore) are preserved or included in the structured output as appropriate.
♻️ Duplicate comments (1)
cmd/cloud/config.go (1)
76-79:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
config showstill exposes the API key in plaintext output.
printOutput(cfg)serializes the fullcliConfigincludingAPIKey, which means the raw key value is printed to stdout regardless of format (JSON/YAML/table). This is the root cause of the three CodeQL findings flagged in past reviews. The fix is to redact the key before rendering.🔧 Proposed fix
var configShowCmd = &cobra.Command{ Use: "show", Short: "Show current configuration", Run: func(cmd *cobra.Command, args []string) { cfg := loadFullConfig() - printOutput(cfg) + type safeConfig struct { + APIKey string `json:"api_key,omitempty"` + APIURL string `json:"api_url,omitempty"` + Output string `json:"output,omitempty"` + Tenant string `json:"tenant,omitempty"` + Debug bool `json:"debug,omitempty"` + } + masked := safeConfig{ + APIKey: maskSecret(cfg.APIKey), + APIURL: cfg.APIURL, + Output: cfg.Output, + Tenant: cfg.Tenant, + Debug: cfg.Debug, + } + printOutput(masked) }, }Where
maskSecretreturns"***"for a non-empty string and""otherwise.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/config.go` around lines 76 - 79, The config show command currently prints the full cliConfig including the APIKey; before calling printOutput(cfg) in the Run handler, redact cfg.APIKey by replacing non-empty values with a masked value (use maskSecret to return "***" for non-empty and "" otherwise) so that loadFullConfig(), the cliConfig struct's APIKey field, and the printOutput(cfg) call never output the raw key; ensure the mask is applied on cfg.APIKey (and any other sensitive fields on cliConfig if present) prior to serialization/rendering.
🧹 Nitpick comments (12)
pkg/sdk/function.go (1)
88-98: 💤 Low valueMissing doc comment on exported
ListFunctionsWithPagination
ListFunctionsWithContextAndPagination(line 101) has a doc comment butListFunctionsWithPaginationdoes not, breaking the symmetry of the public API surface.✏️ Proposed fix
+// ListFunctionsWithPagination returns functions with pagination metadata. func (c *Client) ListFunctionsWithPagination(limit, offset int) ([]*Function, *ListResponse[Function], error) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/sdk/function.go` around lines 88 - 98, Add a descriptive doc comment for the exported ListFunctionsWithPagination function (same style as ListFunctionsWithContextAndPagination) explaining its purpose, parameters (limit, offset), return values, and that it paginates functions; place the comment immediately above the func declaration for ListFunctionsWithPagination so the public API docs are consistent with ListFunctionsWithContextAndPagination.pkg/sdk/compute.go (1)
201-239: ⚡ Quick winMissing context variants for
ListInstanceTypesWithPaginationandListSSHKeysWithPaginationAll other paginated list methods across the SDK have context-aware counterparts (
ListInstancesWithContextAndPagination,ListBucketsWithContextAndPagination,ListDeploymentsWithContextAndPagination,ListFunctionsWithContextAndPagination), butListInstanceTypesWithPaginationandListSSHKeysWithPaginationdo not. This breaks the consistency of the SDK surface area and will require an additive change later if callers need deadline/cancellation propagation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/sdk/compute.go` around lines 201 - 239, Add context-aware counterparts for the two paginated list methods: implement ListInstanceTypesWithContextAndPagination(ctx context.Context, limit, offset int) and ListSSHKeysWithContextAndPagination(ctx context.Context, limit, offset int) following the same pattern as the existing ListInstancesWithContextAndPagination/ListBucketsWithContextAndPagination methods; each should accept a context.Context, call the SDK's context-aware pagination helper (the same helper used by other *WithContextAndPagination methods) to populate a Response[ListResponse[...]] and return the inner Data slice, the ListResponse pointer, and error, mirroring the logic in ListInstanceTypesWithPagination and ListSSHKeysWithPagination but with context propagation.cmd/cloud/container.go (1)
44-45: ⚡ Quick winSilently discarded errors on flag reads violate the no-silent-failures guideline
cmd.Flags().GetIntreturns an error that is silently dropped. Per the coding guidelines: Do not use silent failures — avoid blank identifier assignment like_ = someFunc().🛠️ Proposed fix
- limit, _ := cmd.Flags().GetInt("limit") - offset, _ := cmd.Flags().GetInt("offset") + limit, err := cmd.Flags().GetInt("limit") + if err != nil { + fmt.Printf(containerErrorFormat, err) + return + } + offset, err := cmd.Flags().GetInt("offset") + if err != nil { + fmt.Printf(containerErrorFormat, err) + return + }As per coding guidelines: "Do not use silent failures - avoid blank identifier assignment like
_ = someFunc()."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/container.go` around lines 44 - 45, The code silently discards errors from cmd.Flags().GetInt when reading the "limit" and "offset" flags; change the two calls to capture and check the returned errors (e.g., limit, err := cmd.Flags().GetInt("limit") and offset, err := cmd.Flags().GetInt("offset")), handle them by returning the error from the command (or logging and exiting consistently with surrounding code) so failures are not ignored, and update any surrounding control flow (e.g., the command's Run or RunE) to propagate the error instead of using blank identifier `_`.cmd/cloud/function.go (2)
57-69: ⚡ Quick winInline pagination duplicates the
listWithPaginationhelper — DRY violation
container.go,instance.go, andinstance_type.goall delegate to the sharedlistWithPaginationhelper fromcommon.go. This inline block reimplements the same conditional logic. The same duplication exists inssh_key.go(lines 64–78) andstorage.go(lines 41–55).♻️ Proposed refactor
- var functions []*sdk.Function - var meta *sdk.ListResponse[sdk.Function] - - if limit > 0 || offset > 0 { - var err error - functions, meta, err = client.ListFunctionsWithPagination(limit, offset) - if err != nil { - return err - } - } else { - var err error - functions, err = client.ListFunctions() - if err != nil { - return err - } - } + functions, meta, err := listWithPagination( + client.ListFunctions, + client.ListFunctionsWithPagination, + limit, offset, + ) + if err != nil { + return err + }Verify that
client.ListFunctionsandclient.ListFunctionsWithPaginationmatch the function-type constraints expected bylistWithPagination.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/function.go` around lines 57 - 69, This inline pagination block duplicates shared logic — replace it by calling the common helper listWithPagination instead of reimplementing the conditional; ensure you pass client.ListFunctions and client.ListFunctionsWithPagination (or adapt them to the function signatures expected by listWithPagination), capture its returned functions and meta values into the existing variables (functions, meta) and propagate errors as before, removing the duplicated if/else and keeping behavior identical to other files that use listWithPagination.
51-52: ⚡ Quick winSilently discarded errors on flag reads — same guideline violation as
container.go🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/function.go` around lines 51 - 52, The current reads of command flags silently discard errors (limit, _ := cmd.Flags().GetInt("limit"); offset, _ := cmd.Flags().GetInt("offset")), so change these to capture and handle the errors from cmd.Flags().GetInt for both "limit" and "offset" (e.g., limit, err := cmd.Flags().GetInt("limit"); if err != nil { return fmt.Errorf("reading limit flag: %w", err) } and similarly for offset) — ensure you import/use fmt or the existing logger and return or exit with a clear error instead of ignoring the err to avoid silent failures.cmd/cloud/ssh_key.go (1)
58-59: ⚡ Quick winSilently discarded errors on flag reads — same guideline violation
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/ssh_key.go` around lines 58 - 59, The flag reads for limit and offset currently ignore errors (limit, _ := cmd.Flags().GetInt("limit") and offset, _ := cmd.Flags().GetInt("offset")); update the code to check the returned error from cmd.Flags().GetInt for both "limit" and "offset" and handle it (e.g., return the error from the enclosing command handler or log and exit) instead of discarding it. Locate the flag reads in cmd/cloud/ssh_key.go and adjust the surrounding function (the command handler) to propagate or handle the errors so invalid flag state doesn't go unnoticed.cmd/cloud/storage.go (2)
59-64: ⚡ Quick win
json.MarshalIndenterrors silently discarded — guideline violation
data, _ := json.MarshalIndent(...)silently drops the marshalling error on lines 59 and 62. While rare for plain structs, this is the same silent-failure pattern prohibited by the coding guidelines.🛠️ Proposed fix
if meta != nil { - data, _ := json.MarshalIndent(meta, "", " ") - fmt.Println(string(data)) + data, err := json.MarshalIndent(meta, "", " ") + if err != nil { + fmt.Printf(errFmt, err) + return + } + fmt.Println(string(data)) } else { - data, _ := json.MarshalIndent(buckets, "", " ") - fmt.Println(string(data)) + data, err := json.MarshalIndent(buckets, "", " ") + if err != nil { + fmt.Printf(errFmt, err) + return + } + fmt.Println(string(data)) }Alternatively, use the shared
printJSON()helper (asinstance.godoes) to avoid raw marshalling entirely.As per coding guidelines: "Do not use silent failures - avoid blank identifier assignment like
_ = someFunc()."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/storage.go` around lines 59 - 64, The code currently discards errors from json.MarshalIndent when printing meta and buckets (data, _ := json.MarshalIndent(...)); update these locations to either call the existing shared printJSON(...) helper (used in instance.go) passing meta and buckets, or capture the error (data, err := json.MarshalIndent(...)) and handle it (log or return the error) before printing to avoid silent failures; ensure you reference the json.MarshalIndent calls and replace them with printJSON(meta)/printJSON(buckets) or an explicit err check and proper error handling.
35-36: ⚡ Quick winSilently discarded errors on flag reads — same guideline violation
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/storage.go` around lines 35 - 36, The flag reads for limit and offset call cmd.Flags().GetInt and ignore the returned errors; update the code around the GetInt calls (the lines assigning to limit and offset) to check each error result and handle it (e.g., return the error from the surrounding function, call cmd.PrintErrln()/log/processLogger with a clear message, or fall back to a safe default), so you don't silently discard errors from cmd.Flags().GetInt for the limit and offset flags.cmd/cloud/instance.go (1)
39-40: ⚡ Quick winSilently discarded errors on flag reads — same guideline violation
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/instance.go` around lines 39 - 40, The flag reads for limit and offset call cmd.Flags().GetInt but ignore returned errors; update the code around the GetInt calls (the uses that assign to limit and offset) to check and handle the error values instead of discarding them — e.g., capture both return values, validate err != nil, and propagate or report the error (return it from the enclosing command handler or log via the command's error handling) so invalid or missing flag values are not silently ignored; apply the same pattern to any other cmd.Flags().Get* usages in the same function.cmd/cloud/instance_type.go (1)
22-23: ⚡ Quick winSilently discarded errors on flag reads — same guideline violation
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/instance_type.go` around lines 22 - 23, The code calls cmd.Flags().GetInt for "limit" and "offset" and ignores the returned errors (variables limit, offset), so update those calls to capture the errors and handle them instead of discarding: replace limit, _ := cmd.Flags().GetInt("limit") and offset, _ := cmd.Flags().GetInt("offset") with limit, err := cmd.Flags().GetInt("limit") / offset, err := cmd.Flags().GetInt("offset") (use distinct err vars or check sequentially), check each err and either return a wrapped error from the command's Run/Execute function or log and exit appropriately so invalid flag parsing is surfaced (reference symbols: cmd.Flags().GetInt, limit, offset).cmd/cloud/main.go (1)
87-87: ⚡ Quick win
--jsonis described as deprecated in the help text but Cobra'sMarkDeprecatedis never calledWithout
MarkDeprecated, users receive no runtime warning when using--json. The help text change alone only documents the intent; it doesn't enforce deprecation through the framework.🛠️ Proposed fix
Add after the flag declaration:
rootCmd.PersistentFlags().BoolVarP(&opts.JSON, "json", "j", false, "Output in JSON format (deprecated, use --output=json)") + _ = rootCmd.PersistentFlags().MarkDeprecated("json", "use --output=json instead")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/main.go` at line 87, The flag --json (registered via rootCmd.PersistentFlags().BoolVarP and bound to opts.JSON) is only marked deprecated in help text but not to Cobra; call rootCmd.PersistentFlags().MarkDeprecated("json", "use --output=json") immediately after the BoolVarP registration (handle or ignore the returned error) so Cobra emits the deprecation warning at runtime.cmd/cloud/config.go (1)
68-151: ⚖️ Poor tradeoffPrefer constructor functions over package-level
varcommand declarations.
configCmd,configShowCmd,configSetCmd, andconfigUnsetCmdare all package-level globals. Cobra supports a constructor approach (func newConfigCmd() *cobra.Command) that scopes command variables locally, removes global mutable state, and improves testability.♻️ Proposed refactor
-var configCmd = &cobra.Command{ - Use: "config", - Short: "Manage CLI configuration", -} - -var configShowCmd = &cobra.Command{ ... } -var configSetCmd = &cobra.Command{ ... } -var configUnsetCmd = &cobra.Command{ ... } - -func init() { - configCmd.AddCommand(configShowCmd) - configCmd.AddCommand(configSetCmd) - configCmd.AddCommand(configUnsetCmd) -} +func newConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Manage CLI configuration", + } + cmd.AddCommand(newConfigShowCmd()) + cmd.AddCommand(newConfigSetCmd()) + cmd.AddCommand(newConfigUnsetCmd()) + return cmd +}Wire it from the root command constructor instead of
init().As per coding guidelines: "Do not use global variables" and "Use constructor injection for dependencies instead of global initialization."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cmd/cloud/config.go` around lines 68 - 151, The package-level cobra.Command vars (configCmd, configShowCmd, configSetCmd, configUnsetCmd) should be replaced with constructor functions (e.g., newConfigCmd(), newConfigShowCmd(), newConfigSetCmd(), newConfigUnsetCmd()) that return *cobra.Command; move each Run closure and Args logic into those constructors, keep calls to loadFullConfig and saveConfigFile as-is, and remove the init() wiring—instead have the root command constructor call newConfigCmd() and add its subcommands there to avoid global mutable state and improve testability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/cloud/common.go`:
- Around line 109-119: The current listWithPagination routes to pagFn when
offset > 0 even if limit == 0, which can cause pagFn to be called with limit==0;
update listWithPagination (the function and its use of regularFn/pagFn and the
limit/offset parameters) to avoid invoking pagFn with a zero limit — either
require both limit > 0 && offset > 0 to choose the paginated path, or when
offset > 0 and limit == 0 set a sensible defaultLimit (e.g., 100) before calling
pagFn; ensure the chosen approach is applied where listWithPagination calls
pagFn so pagFn never receives limit == 0 unexpectedly.
- Around line 41-44: The current apiURL selection uses a magic string and
fragile equality check; change the logic in the apiURL assignment to follow
precedence CLI flag → config file → hardcoded default by: introduce a named
constant (e.g. DEFAULT_API_URL) instead of the literal "http://localhost:8080",
then set apiURL to o.APIURL if o.APIURL != "", else to cfg.APIURL if cfg.APIURL
!= "", else to DEFAULT_API_URL; update references around the apiURL variable and
remove the equality check against the magic string in cmd/cloud/common.go.
- Around line 76-97: printOutput currently only reads opts.Output (defaulting to
"table") and never respects the persisted cfg.Output, so saved preferences are
ignored; update command initialization to load the config and set opts.Output =
cfg.Output when the user did not pass an explicit --output flag (e.g., in a
PersistentPreRun hook or before command execution in main.go), ensuring the CLI
flag (opts.Output) remains authoritative if provided but otherwise falls back to
cfg.Output; reference the printOutput function, the opts.Output field, and the
cfg.Output config field when implementing this synchronization.
In `@cmd/cloud/config.go`:
- Around line 12-18: The cliConfig struct's JSON tags should include omitempty
for optional fields to avoid serializing zero values; update the struct
declaration for cliConfig so the tags for APIKey, APIURL, Output, Tenant, and
Debug each include `omitempty` (e.g., `json:"api_key,omitempty"`) so empty
strings and false are omitted during JSON marshaling.
- Around line 96-98: When handling the "output" key in the config set flow (see
the switch that assigns cfg.Output in cmd/cloud/config.go), validate that the
incoming value is one of the allowed values "table", "json", or "yaml" before
assigning and persisting it; if the value is invalid, return an error (or
surface a clear message) and do not set cfg.Output. Update the code path that
currently does cfg.Output = value to perform this whitelist check and reject
invalid values so subsequent commands won't be confused by an unsupported output
setting.
- Line 20: Replace the package-level configFile global with a helper function
configFilePath() that calls os.UserHomeDir() at runtime and constructs
filepath.Join(home, ".cloud", "config.json"), returning (string, error); remove
the var configFile declaration; then update loadConfigFile(), loadFullConfig(),
and saveConfigFile() to call configFilePath(), handle any error returned
(propagate or return it), and use the returned path instead of the former global
variable.
In `@cmd/cloud/instance.go`:
- Around line 52-58: The JSON output currently switches between printing a flat
array (printJSON(instances)) and a paginated envelope (printJSON(meta)) based on
whether meta is non-nil; change the behavior to produce a stable top-level shape
when the user explicitly requests pagination: if opts.JSON and the limit flag
was provided (e.g., check opts.Limit or a boolean like opts.LimitSet), always
print the meta envelope (printJSON(meta)) so callers get {"items":[...],
"totalCount":..., "hasMore":...}; apply the same fix in instance_type.go (lines
with printJSON(meta)/printJSON(instances)) and storage.go (the analogous block)
to ensure consistency across commands, and make sure meta is populated with
items when wrapping so downstream JSON consumers can rely on one schema.
In `@cmd/cloud/main.go`:
- Around line 86-87: List commands currently check opts.JSON directly while the
flag stores into opts.Output, so passing --output json/yaml is ignored; fix by
normalizing and using the central printer: in the root command PersistentPreRun
set opts.JSON = (opts.Output == "json") and (optionally) create a boolean or
enum for yaml handling so downstream checks work, then update each list command
(instance list, storage list, instance-type list, container list, function list,
ssh-key list) to stop rendering tables directly and instead call printOutput()
(or otherwise dispatch via printOutput()) so opts.Output values
("json","yaml","table") are honored consistently.
In `@cmd/cloud/rbac_cli_test.go`:
- Around line 53-67: The test wraps captureStdout and createRoleCmd.Run in an
IIFE with a recover that masks panics (the anonymous deferred recover block
around captureStdout), which hides the root cause; remove that recover wrapper
and instead fix the underlying panic either by ensuring test setup is complete
before invoking createRoleCmd.Run (initialize any required state/flags/args used
by createRoleCmd.Run and captureStdout) or by hardening createRoleCmd.Run and/or
captureStdout to guard against nil/uninitialized values (add proper nil checks
and safe error returns in createRoleCmd.Run and captureStdout rather than
allowing a panic).
In `@docs/cli-reference.md`:
- Around line 207-212: Add a blank line immediately before each markdown table
header row "`| Flag | Default | Description |`" in docs/cli-reference.md where
tables start (the occurrences near the sections referenced in the comment), so
the table is preceded by an empty line to satisfy markdownlint MD058; locate
each table header instance (e.g., the exact string `| Flag | Default |
Description |`) and insert one blank line above it at those spots.
- Around line 82-84: The fenced code block containing the example line "Showing
10 of 50 total (more available)" is missing a language tag and triggers
markdownlint MD040; update that fenced block in docs/cli-reference.md by adding
a language specifier (e.g., "text") immediately after the opening triple
backticks so it becomes ```text ... ``` to satisfy the lint rule.
In `@internal/core/services/cached_identity_unit_test.go`:
- Line 20: Remove the unconditional t.Skip in TestCachedIdentityService_Unit and
make the test deterministic by isolating setup/teardown per subtest: for each
subtest create a fresh miniredis instance and corresponding redis client, wait
for the server to be ready before creating the client, avoid shared global
state, and defer the server and client cleanup; ensure the test uses t.Run
subtests (optionally with t.Parallel where safe) so cache-hit/miss behavior is
validated independently and the nil redis client race is eliminated.
In `@pkg/sdk/client.go`:
- Around line 25-29: The JSON tag on ListResponse[T].TotalCount uses
`omitempty`, which causes zero to be omitted; remove `omitempty` from the
`TotalCount` tag so a zero total_count is serialized as 0 (leave `HasMore`'s
`omitempty` unchanged). Update the struct definition for ListResponse to use
`json:"total_count"` for TotalCount and keep `json:"has_more,omitempty"` for
HasMore so pagination consumers receive an explicit 0 when there are no items.
- Around line 36-42: The custom retry predicate passed to
client.AddRetryCondition currently returns false whenever err != nil, which
prevents Resty from retrying network/transport errors; update the predicate used
in the AddRetryCondition call (the anonymous func(r *resty.Response, err error)
bool) so it returns true when err != nil (or when the error is a transient
network error) and also returns true for status codes >= 500 or 429; in short,
change the logic in the AddRetryCondition closure to include "if err != nil {
return true }" before inspecting r.StatusCode() so SetRetryCount(3) will apply
to network failures as well.
---
Outside diff comments:
In `@cmd/cloud/container.go`:
- Around line 38-78: The listDeploymentsCmd currently always renders a table and
ignores opts.JSON/opts.Output; update listDeploymentsCmd (and mirror the same
fix in listFnCmd and newSSHKeyListCmd) to check opts.JSON or opts.Output and
branch to emit JSON or YAML when requested (e.g., serialize the deps slice using
encoding/json or yaml), or call the shared printOutput() helper if it supports
slices; keep the existing table rendering as the default, and ensure error
handling and meta output (meta.TotalCount/HasMore) are preserved or included in
the structured output as appropriate.
In `@cmd/cloud/rbac_cli_test.go`:
- Around line 23-68: Remove the temporary debug prints added to the
TestCreateRoleCmd test: delete all fmt.Fprintf(os.Stderr, "DEBUG: ...") calls
around the httptest server setup and the captureStdout/panic-recovery block
(they surround createRoleCmd.Run and captureStdout); after removing those debug
statements, also remove the now-unused fmt and os imports from the top of
cmd/cloud/rbac_cli_test.go so there are no leftover compile warnings or CI
noise.
In `@docs/cli-reference.md`:
- Around line 1165-1169: The "JSON Output" section references the deprecated
flags `--json` and `-j`; update the explanatory text and examples to recommend
the supported form `--output json` instead. Change the sentence "Use `--json`
(or `-j`) with any list/get command…" to mention `--output json` (and optionally
`-o json` if applicable), and replace the example `cloud instance list --json |
jq '.[].id'` with one using `cloud instance list --output json | jq '.[].id'` so
the doc is consistent with the deprecation notice.
---
Duplicate comments:
In `@cmd/cloud/config.go`:
- Around line 76-79: The config show command currently prints the full cliConfig
including the APIKey; before calling printOutput(cfg) in the Run handler, redact
cfg.APIKey by replacing non-empty values with a masked value (use maskSecret to
return "***" for non-empty and "" otherwise) so that loadFullConfig(), the
cliConfig struct's APIKey field, and the printOutput(cfg) call never output the
raw key; ensure the mask is applied on cfg.APIKey (and any other sensitive
fields on cliConfig if present) prior to serialization/rendering.
---
Nitpick comments:
In `@cmd/cloud/config.go`:
- Around line 68-151: The package-level cobra.Command vars (configCmd,
configShowCmd, configSetCmd, configUnsetCmd) should be replaced with constructor
functions (e.g., newConfigCmd(), newConfigShowCmd(), newConfigSetCmd(),
newConfigUnsetCmd()) that return *cobra.Command; move each Run closure and Args
logic into those constructors, keep calls to loadFullConfig and saveConfigFile
as-is, and remove the init() wiring—instead have the root command constructor
call newConfigCmd() and add its subcommands there to avoid global mutable state
and improve testability.
In `@cmd/cloud/container.go`:
- Around line 44-45: The code silently discards errors from cmd.Flags().GetInt
when reading the "limit" and "offset" flags; change the two calls to capture and
check the returned errors (e.g., limit, err := cmd.Flags().GetInt("limit") and
offset, err := cmd.Flags().GetInt("offset")), handle them by returning the error
from the command (or logging and exiting consistently with surrounding code) so
failures are not ignored, and update any surrounding control flow (e.g., the
command's Run or RunE) to propagate the error instead of using blank identifier
`_`.
In `@cmd/cloud/function.go`:
- Around line 57-69: This inline pagination block duplicates shared logic —
replace it by calling the common helper listWithPagination instead of
reimplementing the conditional; ensure you pass client.ListFunctions and
client.ListFunctionsWithPagination (or adapt them to the function signatures
expected by listWithPagination), capture its returned functions and meta values
into the existing variables (functions, meta) and propagate errors as before,
removing the duplicated if/else and keeping behavior identical to other files
that use listWithPagination.
- Around line 51-52: The current reads of command flags silently discard errors
(limit, _ := cmd.Flags().GetInt("limit"); offset, _ :=
cmd.Flags().GetInt("offset")), so change these to capture and handle the errors
from cmd.Flags().GetInt for both "limit" and "offset" (e.g., limit, err :=
cmd.Flags().GetInt("limit"); if err != nil { return fmt.Errorf("reading limit
flag: %w", err) } and similarly for offset) — ensure you import/use fmt or the
existing logger and return or exit with a clear error instead of ignoring the
err to avoid silent failures.
In `@cmd/cloud/instance_type.go`:
- Around line 22-23: The code calls cmd.Flags().GetInt for "limit" and "offset"
and ignores the returned errors (variables limit, offset), so update those calls
to capture the errors and handle them instead of discarding: replace limit, _ :=
cmd.Flags().GetInt("limit") and offset, _ := cmd.Flags().GetInt("offset") with
limit, err := cmd.Flags().GetInt("limit") / offset, err :=
cmd.Flags().GetInt("offset") (use distinct err vars or check sequentially),
check each err and either return a wrapped error from the command's Run/Execute
function or log and exit appropriately so invalid flag parsing is surfaced
(reference symbols: cmd.Flags().GetInt, limit, offset).
In `@cmd/cloud/instance.go`:
- Around line 39-40: The flag reads for limit and offset call cmd.Flags().GetInt
but ignore returned errors; update the code around the GetInt calls (the uses
that assign to limit and offset) to check and handle the error values instead of
discarding them — e.g., capture both return values, validate err != nil, and
propagate or report the error (return it from the enclosing command handler or
log via the command's error handling) so invalid or missing flag values are not
silently ignored; apply the same pattern to any other cmd.Flags().Get* usages in
the same function.
In `@cmd/cloud/main.go`:
- Line 87: The flag --json (registered via rootCmd.PersistentFlags().BoolVarP
and bound to opts.JSON) is only marked deprecated in help text but not to Cobra;
call rootCmd.PersistentFlags().MarkDeprecated("json", "use --output=json")
immediately after the BoolVarP registration (handle or ignore the returned
error) so Cobra emits the deprecation warning at runtime.
In `@cmd/cloud/ssh_key.go`:
- Around line 58-59: The flag reads for limit and offset currently ignore errors
(limit, _ := cmd.Flags().GetInt("limit") and offset, _ :=
cmd.Flags().GetInt("offset")); update the code to check the returned error from
cmd.Flags().GetInt for both "limit" and "offset" and handle it (e.g., return the
error from the enclosing command handler or log and exit) instead of discarding
it. Locate the flag reads in cmd/cloud/ssh_key.go and adjust the surrounding
function (the command handler) to propagate or handle the errors so invalid flag
state doesn't go unnoticed.
In `@cmd/cloud/storage.go`:
- Around line 59-64: The code currently discards errors from json.MarshalIndent
when printing meta and buckets (data, _ := json.MarshalIndent(...)); update
these locations to either call the existing shared printJSON(...) helper (used
in instance.go) passing meta and buckets, or capture the error (data, err :=
json.MarshalIndent(...)) and handle it (log or return the error) before printing
to avoid silent failures; ensure you reference the json.MarshalIndent calls and
replace them with printJSON(meta)/printJSON(buckets) or an explicit err check
and proper error handling.
- Around line 35-36: The flag reads for limit and offset call cmd.Flags().GetInt
and ignore the returned errors; update the code around the GetInt calls (the
lines assigning to limit and offset) to check each error result and handle it
(e.g., return the error from the surrounding function, call
cmd.PrintErrln()/log/processLogger with a clear message, or fall back to a safe
default), so you don't silently discard errors from cmd.Flags().GetInt for the
limit and offset flags.
In `@pkg/sdk/compute.go`:
- Around line 201-239: Add context-aware counterparts for the two paginated list
methods: implement ListInstanceTypesWithContextAndPagination(ctx
context.Context, limit, offset int) and ListSSHKeysWithContextAndPagination(ctx
context.Context, limit, offset int) following the same pattern as the existing
ListInstancesWithContextAndPagination/ListBucketsWithContextAndPagination
methods; each should accept a context.Context, call the SDK's context-aware
pagination helper (the same helper used by other *WithContextAndPagination
methods) to populate a Response[ListResponse[...]] and return the inner Data
slice, the ListResponse pointer, and error, mirroring the logic in
ListInstanceTypesWithPagination and ListSSHKeysWithPagination but with context
propagation.
In `@pkg/sdk/function.go`:
- Around line 88-98: Add a descriptive doc comment for the exported
ListFunctionsWithPagination function (same style as
ListFunctionsWithContextAndPagination) explaining its purpose, parameters
(limit, offset), return values, and that it paginates functions; place the
comment immediately above the func declaration for ListFunctionsWithPagination
so the public API docs are consistent with
ListFunctionsWithContextAndPagination.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2bfae54f-e757-44e9-a6aa-99ba8bc0e5d0
📒 Files selected for processing (21)
README.mdcmd/cloud/common.gocmd/cloud/config.gocmd/cloud/container.gocmd/cloud/container_cli_test.gocmd/cloud/function.gocmd/cloud/instance.gocmd/cloud/instance_type.gocmd/cloud/main.gocmd/cloud/rbac_cli_test.gocmd/cloud/sg.gocmd/cloud/ssh_key.gocmd/cloud/storage.godocs/cli-reference.mdinternal/core/services/cached_identity_unit_test.gopkg/sdk/client.gopkg/sdk/compute.gopkg/sdk/container.gopkg/sdk/container_test.gopkg/sdk/function.gopkg/sdk/storage.go
💤 Files with no reviewable changes (1)
- cmd/cloud/sg.go
| apiURL := o.APIURL | ||
| if apiURL == "http://localhost:8080" && cfg.APIURL != "" { | ||
| apiURL = cfg.APIURL | ||
| } |
There was a problem hiding this comment.
Magic string + likely dead code path for config API URL.
"http://localhost:8080" is a magic string (guideline violation), but more importantly the override logic is fragile: if --api-url flag default is "" (empty), this branch is never true and the persisted api_url from ~/.cloud/config.json is never applied. The precedence should be: CLI flag → config file → hardcoded default, not a string equality check against a specific default value.
🔧 Proposed fix
+const defaultAPIURL = "http://localhost:8080"
+
func createClient(o CLIOptions) *sdk.Client {
cfg := loadFullConfig()
...
- apiURL := o.APIURL
- if apiURL == "http://localhost:8080" && cfg.APIURL != "" {
- apiURL = cfg.APIURL
- }
+ apiURL := o.APIURL
+ if (apiURL == "" || apiURL == defaultAPIURL) && cfg.APIURL != "" {
+ apiURL = cfg.APIURL
+ }
+ if apiURL == "" {
+ apiURL = defaultAPIURL
+ }As per coding guidelines: "Do not use magic numbers — use named constants instead."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| apiURL := o.APIURL | |
| if apiURL == "http://localhost:8080" && cfg.APIURL != "" { | |
| apiURL = cfg.APIURL | |
| } | |
| const defaultAPIURL = "http://localhost:8080" | |
| func createClient(o CLIOptions) *sdk.Client { | |
| cfg := loadFullConfig() | |
| ... | |
| apiURL := o.APIURL | |
| if (apiURL == "" || apiURL == defaultAPIURL) && cfg.APIURL != "" { | |
| apiURL = cfg.APIURL | |
| } | |
| if apiURL == "" { | |
| apiURL = defaultAPIURL | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/cloud/common.go` around lines 41 - 44, The current apiURL selection uses
a magic string and fragile equality check; change the logic in the apiURL
assignment to follow precedence CLI flag → config file → hardcoded default by:
introduce a named constant (e.g. DEFAULT_API_URL) instead of the literal
"http://localhost:8080", then set apiURL to o.APIURL if o.APIURL != "", else to
cfg.APIURL if cfg.APIURL != "", else to DEFAULT_API_URL; update references
around the apiURL variable and remove the equality check against the magic
string in cmd/cloud/common.go.
| func printOutput(data interface{}) { | ||
| output := opts.Output | ||
| if output == "" { | ||
| output = "table" | ||
| } | ||
|
|
||
| switch output { | ||
| case "json": | ||
| printJSON(data) | ||
| case "yaml": | ||
| b, err := yaml.Marshal(data) | ||
| if err != nil { | ||
| fmt.Printf("Error marshaling YAML: %v\n", err) | ||
| return | ||
| } | ||
| fmt.Println(string(b)) | ||
| default: | ||
| // For table output, fall back to JSON for now | ||
| // Commands that use tablewriter handle their own output | ||
| printJSON(data) | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether main.go or a PersistentPreRun hook syncs cfg.Output -> opts.Output
rg -n "opts.Output\s*=" --type go
rg -n "cfg\.Output" --type goRepository: poyrazK/thecloud
Length of output: 148
🏁 Script executed:
# Find the definition of opts and cfg
rg -n "var opts" --type go -A 3
rg -n "type.*opts" --type go -A 5
rg -n "loadFullConfig" --type go -B 2 -A 10Repository: poyrazK/thecloud
Length of output: 2846
🏁 Script executed:
# Check main.go and initialization flow
git ls-files "**/*.go" | grep -E "(main|cmd)" | head -20Repository: poyrazK/thecloud
Length of output: 513
🏁 Script executed:
# Look at config.go to understand the config structure
wc -l cmd/cloud/config.goRepository: poyrazK/thecloud
Length of output: 84
🏁 Script executed:
# Get CLIOptions and cliConfig struct definitions
rg -n "type CLIOptions" --type go -A 15
rg -n "type cliConfig" --type go -A 15Repository: poyrazK/thecloud
Length of output: 1416
🏁 Script executed:
# Check main.go for opts initialization and command setup
fd main.go | head -5 | xargs -I {} sh -c 'wc -l {} && echo "---"'
cat cmd/cloud/main.go 2>/dev/null || cat cmd/*/main.go | head -100Repository: poyrazK/thecloud
Length of output: 3247
🏁 Script executed:
# Look for where opts fields are set (CLI flags setup)
rg -n "opts\." --type go | grep -v "opts.Output" | head -20Repository: poyrazK/thecloud
Length of output: 1502
🏁 Script executed:
# Check for PersistentPreRun or PersistentPreRunE hooks in main.go and command definitions
rg -n "PersistentPre" --type goRepository: poyrazK/thecloud
Length of output: 42
🏁 Script executed:
# Double-check: look at all places where opts.Output is actually read/used
rg -n "opts\.Output" --type goRepository: poyrazK/thecloud
Length of output: 240
printOutput ignores persisted output format from config file.
The opts.Output field (line 77) is populated only from CLI flags with a default of "table" (main.go:86). When a user runs cloud config set output yaml, that preference is saved to cfg.Output in the config file, but printOutput never reads it. There is no sync between cfg.Output and opts.Output, so the persisted config setting is silently ignored every time a command calls printOutput.
To fix this, load the config file output preference early (e.g., in a PersistentPreRun hook or before command execution) and set opts.Output = cfg.Output when no explicit --output flag is provided.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/cloud/common.go` around lines 76 - 97, printOutput currently only reads
opts.Output (defaulting to "table") and never respects the persisted cfg.Output,
so saved preferences are ignored; update command initialization to load the
config and set opts.Output = cfg.Output when the user did not pass an explicit
--output flag (e.g., in a PersistentPreRun hook or before command execution in
main.go), ensuring the CLI flag (opts.Output) remains authoritative if provided
but otherwise falls back to cfg.Output; reference the printOutput function, the
opts.Output field, and the cfg.Output config field when implementing this
synchronization.
| func listWithPagination[T any]( | ||
| regularFn func() ([]T, error), | ||
| pagFn func(int, int) ([]T, *sdk.ListResponse[T], error), | ||
| limit, offset int, | ||
| ) ([]T, *sdk.ListResponse[T], error) { | ||
| if limit > 0 || offset > 0 { | ||
| return pagFn(limit, offset) | ||
| } | ||
| items, err := regularFn() | ||
| return items, nil, err | ||
| } |
There was a problem hiding this comment.
offset > 0 with limit == 0 may trigger an unintended paginated call.
The condition limit > 0 || offset > 0 routes to pagFn when only offset is provided (e.g., --offset 5 without --limit). Depending on the SDK's behavior for limit=0, this could return zero results or trigger an error, rather than the caller's expectation of "skip 5, return all".
Consider requiring both limit and offset to activate the paginated path, or defaulting limit to a sensible value when only offset is specified.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/cloud/common.go` around lines 109 - 119, The current listWithPagination
routes to pagFn when offset > 0 even if limit == 0, which can cause pagFn to be
called with limit==0; update listWithPagination (the function and its use of
regularFn/pagFn and the limit/offset parameters) to avoid invoking pagFn with a
zero limit — either require both limit > 0 && offset > 0 to choose the paginated
path, or when offset > 0 and limit == 0 set a sensible defaultLimit (e.g., 100)
before calling pagFn; ensure the chosen approach is applied where
listWithPagination calls pagFn so pagFn never receives limit == 0 unexpectedly.
| Debug bool `json:"debug"` | ||
| } | ||
|
|
||
| var configFile = filepath.Join(os.Getenv("HOME"), ".cloud", "config.json") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the file structure and locate the file
find . -name "config.go" -path "*/cloud/*" | head -20Repository: poyrazK/thecloud
Length of output: 82
🏁 Script executed:
# Check the specific line and surrounding context in cmd/cloud/config.go
if [ -f "cmd/cloud/config.go" ]; then
wc -l cmd/cloud/config.go
fiRepository: poyrazK/thecloud
Length of output: 84
🏁 Script executed:
# Read the file with line numbers, focusing on the area around line 20
if [ -f "cmd/cloud/config.go" ]; then
head -50 cmd/cloud/config.go | cat -n
fiRepository: poyrazK/thecloud
Length of output: 1262
🏁 Script executed:
# Search for all uses of configFile variable in the file
if [ -f "cmd/cloud/config.go" ]; then
rg -n "configFile" cmd/cloud/config.go
fiRepository: poyrazK/thecloud
Length of output: 321
🏁 Script executed:
# Check the Go documentation and standard library for os.UserHomeDir vs os.Getenv
# os.UserHomeDir is a standard function in Go 1.12+
# Let's verify the Go version requirement in the project
fd go.mod -x head -5 {}Repository: poyrazK/thecloud
Length of output: 117
🏁 Script executed:
# Read lines 50-70 to see the saveConfigFile function
sed -n '50,70p' cmd/cloud/config.go | cat -nRepository: poyrazK/thecloud
Length of output: 754
🏁 Script executed:
# Check if there are any other global variables or initialization in the file
rg -n "^var |^const " cmd/cloud/config.go | head -20Repository: poyrazK/thecloud
Length of output: 291
Replace the package-level global configFile variable with a helper function using os.UserHomeDir().
This code has two issues:
- Global variable violation:
var configFileis a package-level global variable, which violates the coding guidelines. - Cross-platform bug:
os.Getenv("HOME")is evaluated at package initialization time and is unset on Windows (where%USERPROFILE%is used instead). This causes the config path to silently resolve incorrectly on non-Unix platforms. Useos.UserHomeDir()instead, which is cross-platform and available in Go 1.12+.
The config path should be computed at runtime inside each helper function. Update loadConfigFile(), loadFullConfig(), and saveConfigFile() to call a new helper that computes the path and propagates errors.
🔧 Proposed fix
-var configFile = filepath.Join(os.Getenv("HOME"), ".cloud", "config.json")
+const configDirName = ".cloud"
+const configFileName = "config.json"
+
+func configFilePath() (string, error) {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", fmt.Errorf("could not determine home directory: %w", err)
+ }
+ return filepath.Join(home, configDirName, configFileName), nil
+}Then update loadConfigFile(), loadFullConfig(), and saveConfigFile() to call configFilePath() and handle the returned error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmd/cloud/config.go` at line 20, Replace the package-level configFile global
with a helper function configFilePath() that calls os.UserHomeDir() at runtime
and constructs filepath.Join(home, ".cloud", "config.json"), returning (string,
error); remove the var configFile declaration; then update loadConfigFile(),
loadFullConfig(), and saveConfigFile() to call configFilePath(), handle any
error returned (propagate or return it), and use the returned path instead of
the former global variable.
| ``` | ||
| Showing 10 of 50 total (more available) | ||
| ``` |
There was a problem hiding this comment.
Fenced code block missing language specifier (markdownlint MD040)
The output example block on line 82 has no language tag. Add text (or another appropriate identifier) to satisfy MD040.
🛠️ Proposed fix
-```
+```text
Showing 10 of 50 total (more available)
-```
+```🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 82-82: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/cli-reference.md` around lines 82 - 84, The fenced code block containing
the example line "Showing 10 of 50 total (more available)" is missing a language
tag and triggers markdownlint MD040; update that fenced block in
docs/cli-reference.md by adding a language specifier (e.g., "text") immediately
after the opening triple backticks so it becomes ```text ... ``` to satisfy the
lint rule.
| **Flags**: | ||
| | Flag | Default | Description | | ||
| |------|---------|-------------| | ||
| | `--limit` | `0` | Maximum number of results (0 = use server default) | | ||
| | `--offset` | `0` | Number of results to skip | | ||
|
|
There was a problem hiding this comment.
Tables missing surrounding blank lines (markdownlint MD058)
The flag tables at lines 208, 310, 324, 367, 798, and 1060 are not preceded by a blank line. Add a blank line before each | Flag | header row.
Example fix for lines 207–212:
🛠️ Proposed fix (representative example)
**Flags**:
+
| Flag | Default | Description |Also applies to: 309-314, 323-328, 366-371, 797-802, 1059-1064
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 208-208: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/cli-reference.md` around lines 207 - 212, Add a blank line immediately
before each markdown table header row "`| Flag | Default | Description |`" in
docs/cli-reference.md where tables start (the occurrences near the sections
referenced in the comment), so the table is preceded by an empty line to satisfy
markdownlint MD058; locate each table header instance (e.g., the exact string `|
Flag | Default | Description |`) and insert one blank line above it at those
spots.
| // doesn't properly interact with miniredis. The test needs to be fixed | ||
| // to correctly verify cached identity service behavior. | ||
| t.Skip("Skipping flaky test - see issue #354") | ||
| t.Skip("Skipping flaky test - miniredis race condition causes nil redis client") |
There was a problem hiding this comment.
Avoid unconditionally skipping the entire unit suite.
Line 20 disables all coverage for TestCachedIdentityService_Unit, which can mask regressions in cache-hit/miss behavior. Please make the test deterministic instead (e.g., isolate setup per subtest and fix the race) rather than skipping it globally.
As per coding guidelines, "Do not skip tests for new services".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/core/services/cached_identity_unit_test.go` at line 20, Remove the
unconditional t.Skip in TestCachedIdentityService_Unit and make the test
deterministic by isolating setup/teardown per subtest: for each subtest create a
fresh miniredis instance and corresponding redis client, wait for the server to
be ready before creating the client, avoid shared global state, and defer the
server and client cleanup; ensure the test uses t.Run subtests (optionally with
t.Parallel where safe) so cache-hit/miss behavior is validated independently and
the nil redis client race is eliminated.
| type ListResponse[T any] struct { | ||
| Data []T `json:"data"` | ||
| TotalCount int `json:"total_count,omitempty"` | ||
| HasMore bool `json:"has_more,omitempty"` | ||
| } |
There was a problem hiding this comment.
TotalCount with omitempty drops zero values during marshaling
omitempty on an int field omits it from JSON when the value is 0. If an empty result set truly has total_count: 0, CLI consumers or API callers serializing this struct will see the field absent rather than 0, which can be misleading for pagination logic.
🛠️ Proposed fix
type ListResponse[T any] struct {
Data []T `json:"data"`
- TotalCount int `json:"total_count,omitempty"`
- HasMore bool `json:"has_more,omitempty"`
+ TotalCount int `json:"total_count"`
+ HasMore bool `json:"has_more,omitempty"`
}HasMore being false on the last page is semantically meaningful (no ambiguity — absent and false behave the same for booleans), so omitempty on HasMore is fine to keep.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type ListResponse[T any] struct { | |
| Data []T `json:"data"` | |
| TotalCount int `json:"total_count,omitempty"` | |
| HasMore bool `json:"has_more,omitempty"` | |
| } | |
| type ListResponse[T any] struct { | |
| Data []T `json:"data"` | |
| TotalCount int `json:"total_count"` | |
| HasMore bool `json:"has_more,omitempty"` | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/sdk/client.go` around lines 25 - 29, The JSON tag on
ListResponse[T].TotalCount uses `omitempty`, which causes zero to be omitted;
remove `omitempty` from the `TotalCount` tag so a zero total_count is serialized
as 0 (leave `HasMore`'s `omitempty` unchanged). Update the struct definition for
ListResponse to use `json:"total_count"` for TotalCount and keep
`json:"has_more,omitempty"` for HasMore so pagination consumers receive an
explicit 0 when there are no items.
| client.AddRetryCondition(func(r *resty.Response, err error) bool { | ||
| if err != nil { | ||
| return false | ||
| } | ||
| statusCode := r.StatusCode() | ||
| return statusCode >= 500 || statusCode == 429 | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the actual code in the repository
cat -n pkg/sdk/client.go | head -50Repository: poyrazK/thecloud
Length of output: 1531
🌐 Web query:
Resty v2 AddRetryCondition documentation retry behavior
💡 Result:
In Resty v2, AddRetryCondition/AddRetryConditions controls when a request is retried by evaluating retry condition functions against the previous attempt’s result (a non-nil *resty.Response or an execution error). A retry happens when any condition returns true. 1) Where retry conditions are added - Client-level: client.AddRetryConditions(...) / client.AddRetryCondition(...). These apply to all requests made by that client. [1] - Request-level: client.R.AddRetryConditions(...) adds conditions just for that request. [1] 2) Precedence / ordering (defaults vs your conditions) - Resty applies its default retry conditions first, before user-defined retry conditions. This is explicitly documented in the retry mechanism guide (including request-level examples). [1] - Additionally, request-level retry conditions are executed before client-level retry conditions. [2] 3) Override vs “extend” semantics - Adding retry conditions overrides the default behavior if you don’t include logic equivalent to the default error retry. Resty’s v2 README states that using AddRetryCondition “overrides the default retry behavior, which retries on errors encountered during the request.” To keep retrying on errors, you must include err!= nil (or equivalent) in your condition(s). [3] 4) Retry decision function inputs - RetryConditionFunc receives (res *resty.Response, err error), where res is nil when no HTTP response was received (e.g., network error), and err is nil when the request completed without a transport error. [4] 5) How multiple conditions are combined - If multiple retry conditions are specified, a retry occurs if any of the conditions returns true. [3][2] - Conditions are evaluated on each retry attempt, and Resty stops evaluating the remaining conditions once one returns true. [2] 6) Retry timing behavior (what governs the wait between attempts) - Resty uses backoff to increase retry intervals after each attempt. [3] - The default delay strategy is “capped exponential backoff with jitter.” [5] - The strategy also respects the Retry-After header (when present) for relevant responses (e.g., 429 and 503 in the implementation shown). [6][5] 7) Practical example: “retry on 429 and on transport errors” - The documented pattern is to include err!= nil to emulate the default retry-on-error behavior, plus your status-code checks. [3] Top authoritative sources used - Resty “Retry Mechanism” docs for AddRetryConditions behavior, ordering with default conditions, and request-level vs client-level usage. [1] - Resty v2 README for override semantics of AddRetryCondition and the “err!= nil || status-code condition” example. [3] - Resty v2 client.go/retry.go for evaluation semantics (any condition true; executed each attempt; request before client) and default backoff strategy details. [2][5]
Citations:
- 1: https://resty.dev/docs/retry-mechanism
- 2: https://github.com/go-resty/resty/blob/master/client.go
- 3: https://github.com/go-resty/resty/blob/v2/README.md
- 4: https://github.com/go-resty/resty/blob/v3/retry.go
- 5: https://github.com/go-resty/resty/blob/c6390812/retry.go
- 6: https://resty.dev/docs/new-features-and-enhancements
🌐 Web query:
Resty golang library AddRetryCondition error handling retry logic
💡 Result:
In the go-resty library (commonly called Resty), AddRetryCondition configures custom retry logic for HTTP requests, including error handling. It accepts one or more RetryConditionFunc functions of type func(*resty.Response, error) bool. These functions receive the response (nil on transport/network errors) and error from the previous attempt. Return true to retry, false to stop [1][2]. Default retry conditions apply first (e.g., status >=500 except 501, 429, temporary network errors), then user-defined ones. All must return true for retry to occur [1]. Can be set at client level: client.AddRetryConditions( func(res *resty.Response, err error) bool { if err != nil { // Retry on specific errors, e.g., network issues return strings.Contains(err.Error(), "no such host") } return res.StatusCode() >= 500 }, ) Or request level with R.AddRetryConditions(...) or SetRetryConditions(...) to overwrite [1][3]. Resty skips retry for non-retryable errors like TLS cert issues via isDoNotRetryError [2]. Only idempotent methods retry by default [4]. Example for error retry (confirmed working in issues) [5][6]: client. SetRetryCount(3). AddRetryCondition(func(r *resty.Response, err error) bool { return err != nil // Retry on any error }) Use AddRetryHook for side effects like logging/metrics before retry [1][2]. Official docs: https://resty.dev/docs/retry-mechanism [1]. Source v3: https://github.com/go-resty/resty/blob/v3/retry.go [2].
Citations:
- 1: https://resty.dev/docs/retry-mechanism
- 2: https://github.com/go-resty/resty/blob/v3/retry.go
- 3: Overwrite Retryconditions go-resty/resty#868
- 4: https://deepwiki.com/go-resty/resty/3.1-retry-mechanism
- 5: Retry on Error go-resty/resty#471
- 6: Retry with new authorization token go-resty/resty#548
AddRetryCondition suppresses retries for network errors
When you specify AddRetryCondition, it overrides Resty's default retry behavior, which retries on errors encountered during the request. The current condition returns false when err != nil, which means transient network failures (connection timeouts, DNS failures, TCP resets) will not be retried, despite SetRetryCount(3) being configured. Only HTTP 5xx and 429 responses are retried.
Fix
client.AddRetryCondition(func(r *resty.Response, err error) bool {
- if err != nil {
- return false
- }
- statusCode := r.StatusCode()
- return statusCode >= 500 || statusCode == 429
+ if err != nil {
+ return true
+ }
+ statusCode := r.StatusCode()
+ return statusCode >= 500 || statusCode == 429
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| client.AddRetryCondition(func(r *resty.Response, err error) bool { | |
| if err != nil { | |
| return false | |
| } | |
| statusCode := r.StatusCode() | |
| return statusCode >= 500 || statusCode == 429 | |
| }) | |
| client.AddRetryCondition(func(r *resty.Response, err error) bool { | |
| if err != nil { | |
| return true | |
| } | |
| statusCode := r.StatusCode() | |
| return statusCode >= 500 || statusCode == 429 | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/sdk/client.go` around lines 36 - 42, The custom retry predicate passed to
client.AddRetryCondition currently returns false whenever err != nil, which
prevents Resty from retrying network/transport errors; update the predicate used
in the AddRetryCondition call (the anonymous func(r *resty.Response, err error)
bool) so it returns true when err != nil (or when the error is a transient
network error) and also returns true for status codes >= 500 or 429; in short,
change the logic in the AddRetryCondition closure to include "if err != nil {
return true }" before inspecting r.StatusCode() so SetRetryCount(3) will apply
to network failures as well.
The crash happens inside captureStdout. Replace it with direct os.Stdout redirection to see if the crash is in captureStdout helper or in the command itself.
…ccurs Call the command directly first without any stdout redirection, then do the capture. If "DEBUG: createRoleCmd.Run completed" appears in CI logs but then the test still fails, the crash is in test infrastructure.
The bug: auth.go used os.UserHomeDir() while config.go used os.Getenv("HOME").
In CI, HOME is set to a temp directory, but os.UserHomeDir() may return
a different path on some Linux configurations. This caused saveConfig to
write to one location while createClient read from another.
Now both use getConfigFilePath() which calls os.UserHomeDir() consistently.
…leCmd The crash in CI was caused by HOME/env mismatch between saveConfig and createClient. Now CLOUD_API_KEY env var is set, bypassing the config file lookup entirely. Also restored simpler captureStdout pattern.
If CLOUD_API_KEY env var IS set but test still fails, we'll see "TESTv5: TestCreateRoleCmd starting" followed by "[WARN] No API Key". This proves the test binary has the env var but something else is wrong.
The crash was caused by HOME/env mismatch in CI. CI sets HOME to a temp directory, but the test also uses CLOUD_API_KEY env var. However, createClient checks env var BEFORE the flag value is considered. Wait - I verified that env var IS checked. So why does it fail? Actually, the issue is that in CI, t.Setenv doesn't work the same way. The env var IS set (we see "TESTv5" debug output), but createClient still prints "[WARN] No API Key". Wait - look at the CI log output for run 25498478511: "DEBUG: TestCreateRoleCmd starting" - not "TESTv5" So the CI was running an OLD version of the code. My "TESTv5" change was never run in CI because GitHub didn't pick up the new commit. Let me push this fix now and see if CI passes with opts.APIKey approach.
Clean up test by using the captureStdout helper instead of direct pipe capture, and remove fmt, io, os imports that are no longer needed.
Summary
SetRetryCount(3)with automatic retry on 5xx server errors and 429 rate limitsListResponse[T]struct andgetWithPagination()method for paginated list requestscloud completion [bash|zsh|fish|powershell]commandcloud config show|set|unsetfor persistent configuration in~/.cloud/config.json--output table|json|yamlflag (default: table), deprecate--jsonalias--debugflag to enable REST API call tracing--limitand--offsetflags to list commands:instance listfunction listcontainer liststorage listssh-key listinstance-type listcli-reference.mdandREADME.mdwith new CLI featuresTest plan
go build ./cmd/cloud/- compiles successfullygo test ./cmd/cloud/...- all tests passgo build ./pkg/sdk/...- SDK compiles successfullygo test ./pkg/sdk/...- SDK tests passSummary by CodeRabbit
New Features
--outputflag for JSON, YAML, table formats)cloud configcommand (set, show, unset operations)--limit/--offsetflags) for list commands across resources--debug) for CLI troubleshootingDocumentation